/* PAM IP Tables module

   This module will insert an iptables rule to allow traffic from the 
   users machine to pass through when he begins his session. 
   
   Here is a sample /etc/pam.d/login file for Debian GNU/Linux
   2.1:
   
   auth       requisite  pam_securetty.so
   auth       sufficient pam_ldap.so
   auth       required   pam_pwdb.so
   auth       optional   pam_group.so
   auth       optional   pam_mail.so
   account    requisite  pam_time.so
   account    sufficient pam_ldap.so
   account    required   pam_pwdb.so
   session    required   pam_iptables.so 
   session    required   pam_pwdb.so
   session    optional   pam_lastlog.so
   password   required   pam_pwdb.so   
   
   Released under the GNU LGPL version 2 or later
   Taken from pam_mkhomedir originally written by 
   Jason Gunthorpe <jgg@debian.org> Feb 1999
   Structure taken from pam_lastlogin by Andrew Morgan 
     <morgan@parc.power.net> 1996
*/

/*
Nathan Zorn <zornnh@musc.edu> (Original Code)
Christopher C. Weis <christopher-c-weis@uiowa.edu> (Modifications for external scripts)
*/

#define _GNU_SOURCE 1
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "pam_iptables.h"

/*
 * here, we make a definition for the externally accessible function
 * in this file (this definition is required for static a module
 * but strongly encouraged generally) it is used to instruct the
 * modules include file to define the function prototypes.
 */

#define PAM_SM_SESSION

#include <security/pam_modules.h>
#include <security/_pam_macros.h>

/* argument parsing */
#define IPTABLES_DEBUG      020	/* keep quiet about things */
#define IPTABLES_QUIET      040	/* keep quiet about things */

static char interface[BUFSIZ] = "eth0";
/* some syslogging */
static void _log_err(int err, const char *format, ...)
{
    va_list args;

    va_start(args, format);
    openlog("pam_iptables", LOG_CONS|LOG_PID, LOG_FACILITY);
    vsyslog(err, format, args);
    va_end(args);
    closelog();
}

static int _pam_parse(int flags, int argc, const char **argv)
{
   int ctrl = 0;

   /* does the appliction require quiet? */
   if ((flags & PAM_SILENT) == PAM_SILENT)
      ctrl |= IPTABLES_QUIET;

   /* step through arguments */
   for (; argc-- > 0; ++argv)
   {
      if (!strncmp(*argv, "silent",6))
      {
	 ctrl |= IPTABLES_QUIET;
      } 
      else if (!strncmp(*argv,"debug",5))
	ctrl |= IPTABLES_DEBUG;

      else if (!strncmp(*argv,"interface=",10))
	strncpy(interface, *argv,10);
      else 
      {
	 _log_err(LOG_ERR, "unknown option; %s", *argv);
      }
   }

   D(("ctrl = %o", ctrl));
   return ctrl;
}

/* This common function is used to send a message to the applications 
   conversion function. Our only use is to ask the application to print 
   an informative message that we are creating the iptable rule. */
static int converse(pam_handle_t * pamh, int ctrl, int nargs
		    ,struct pam_message **message
		    ,struct pam_response **response)
{
   int retval;
   struct pam_conv *conv;

   D(("begin to converse"));

   retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
   if (retval == PAM_SUCCESS)
   {

      retval = conv->conv(nargs, (const struct pam_message **) message
			  ,response, conv->appdata_ptr);

      D(("returned from application's conversation function"));

      if (retval != PAM_SUCCESS && (ctrl & IPTABLES_DEBUG))
      {
	 _log_err(LOG_DEBUG, "conversation failure [%s]"
		  ,pam_strerror(pamh, retval));
      }

   }
   else
   {
      _log_err(LOG_ERR, "couldn't obtain coversation function [%s]"
	       ,pam_strerror(pamh, retval));
   }

   D(("ready to return from module conversation"));

   return retval;		/* propagate error status */
}

/* Ask the application to display a short text string for us. */
static int make_remark(pam_handle_t * pamh, int ctrl, const char *remark)
{
   int retval;

   if ((ctrl & IPTABLES_QUIET) != IPTABLES_QUIET)
   {
      struct pam_message msg[1], *mesg[1];
      struct pam_response *resp = NULL;

      mesg[0] = &msg[0];
      msg[0].msg_style = PAM_TEXT_INFO;
      msg[0].msg = remark;

      retval = converse(pamh, ctrl, 1, mesg, &resp);

      msg[0].msg = NULL;
      if (resp)
      {
	 _pam_drop_reply(resp, 1);
      }
   }
   else
   {
      D(("keeping quiet"));
      retval = PAM_SUCCESS;
   }

   D(("returning %s", pam_strerror(pamh, retval)));
   return retval;
}
/*fork off the iptables command to insert the filter rule*/
int exec_iptables(char **args) 
{
  int retval,child,status;
  pid_t rc;
  char *str_err;   
   child = fork();

   if(child == 0) {     
     /*attempt to execute iptables tool */
     status = execv("FIREWALL_SCRIPT",args);
     if (status == -1 ) {
       /* uh oh an error occured */
       str_err = strerror(errno);
       _log_err(LOG_ERR, str_err);
       retval = PAM_SESSION_ERR;
     }
       
   } else if (child >0) {
     
     /*wait for child*/
     rc = waitpid(child, &retval, 0);
     
     
     if (rc == 0) { /* no child to wait on */
       str_err = strerror(errno);
       _log_err(LOG_ERR, str_err);
     }
     if (WIFEXITED(retval)) {
       if (WEXITSTATUS(retval) == 0) {

	 retval = PAM_SUCCESS;
	 //_log_err(LOG_DEBUG, "Rule put in place.");
	 
       } else {
	 str_err = strerror(errno);
	 _log_err(LOG_ERR, str_err);
	 retval = PAM_SESSION_ERR;
       }
     } else {
       retval = PAM_SESSION_ERR;
     }
     
     /*retval = (retval == 0) ? PAM_SUCCESS:PAM_AUTH_ERR;*/  
   
   } else {
     D(("fork failed"));
     retval = PAM_SESSION_ERR;
   }

   return retval;
}

/* Do the actual work of creating the iptable rule. */
static int insert_rule(char *rhost)
{

  char *args[] = { "%s",rhost, "INSERT_ARGS", NULL };
  return exec_iptables(args);
}

/* Remove the rule once logged out. */
static int delete_rule(char *rhost)
{
  char *args[] = { "%s",rhost, "REMOVE_ARGS", NULL };
  return exec_iptables(args);  
}



/* --- authentication management functions (only) --- */

PAM_EXTERN
int pam_sm_open_session(pam_handle_t * pamh, int flags, int argc
			,const char **argv)
{
   int rhost_retval,retval, ctrl;
   const char *user;
   char *rhost;
   const struct passwd *pwd;
      
   /* Parse the flag values */
   ctrl = _pam_parse(flags, argc, argv);

   /* Determine the user name so we can get the home directory */
   retval = pam_get_item(pamh, PAM_USER, (const void **) &user);
   if (retval != PAM_SUCCESS || user == NULL || *user == '\0')
   {
      _log_err(LOG_NOTICE, "user unknown");
      return PAM_USER_UNKNOWN;
   }
   
   /* Get the password entry */
   pwd = getpwnam(user);
   if (pwd == NULL)
   {
      D(("couldn't identify user %s", user));
      return PAM_CRED_INSUFFICIENT;
   }
   /* Get the remote host name so that we can make the iptable entry */   
   rhost_retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost);
   if (rhost_retval != PAM_SUCCESS || rhost == NULL || *rhost == '\0')
     {
       _log_err(LOG_NOTICE, "Can't get remote host.");
       return PAM_SESSION_ERR;
     }

   /*if a rule already exists remove it and add a new one */
   _log_err(LOG_NOTICE, "User %s logging in from %s", user, rhost);
   return insert_rule(rhost);
}

/* Ignore */
PAM_EXTERN 
int pam_sm_close_session(pam_handle_t * pamh, int flags, int argc
			 ,const char **argv)
{
  int rhost_retval,user_retval;
  char *rhost;
  char *user;

  /* Determine the user name so we can get the home directory */
  user_retval = pam_get_item(pamh, PAM_USER, (const void **) &user);
  if (user_retval != PAM_SUCCESS || user == NULL || *user == '\0')
  {
    _log_err(LOG_NOTICE, "user unknown");
    return PAM_USER_UNKNOWN;
  }

  /* Get the remote host name so that we can make the iptable entry */   
  rhost_retval = pam_get_item(pamh, PAM_RHOST, (const void **)&rhost);
  if (rhost_retval != PAM_SUCCESS || rhost == NULL || *rhost == '\0')
    {
      _log_err(LOG_NOTICE, "Can't get remote host.");
      return PAM_SESSION_ERR;
    }

  /*remove the iptable rule*/
  _log_err(LOG_NOTICE, "User %s logging out from %s", user, rhost);
  return (delete_rule(rhost));
}

#ifdef PAM_STATIC

/* static module data */
struct pam_module _pam_iptables_modstruct =
{
   "pam_iptables",
   NULL,
   NULL,
   NULL,
   pam_sm_open_session,
   pam_sm_close_session,
   NULL,
};

#endif
